﻿// Copyright (c) 2013 Bryan C. O'Connell
// http://wsubi.codeplex.com/license
// Purpose: Itamae translates to "the sushi chef". This class is used to build and manage
// the "Rolls" for wsubi - the information stores on your scripts for use at runtime.

using System;
using System.IO;
using System.Linq;
using System.Diagnostics;
using System.Collections.Generic;

namespace Wsubi
{
    class Itamae
    {
        public Itamae(string ScriptsFolder)  
        {
            if (Directory.Exists(ScriptsFolder))
            {
                _ScriptsFolder = ScriptsFolder;
                _Ready = true;
            }
        }        

        #region Public Methods

        /// <summary>Collects and builds information stores on the files in the 'scripts' folder.</summary>
        /// <param name="Cmd">Command to process. Only 'roll' commands will run.</param>
        public void MakeRolls(Command Cmd)
        {
            CheckReadyState();
            CheckCommandType(Cmd, CommandTypes.Roll);

            if (Cmd.Target != "*")
            {
                throw new ArgumentException("Target was not understood. Roll command is 'roll *'.");
            }

            _ListOfScripts = new DirectoryInfo(_ScriptsFolder).GetFiles().Select(file => file.Name).ToArray();

            BuildFileBin(_ScriptsFolder, _ListOfScripts, _ScriptFileBin = new Dictionary<string,string>());

            _CurrentOrder = null;
        }

        /// <summary>Builds and returns a list of scripts from memory stores.</summary>
        /// <param name="Cmd">Command to process. Only 'list' commands will run.</param>
        public void ListScripts(Command Cmd)
        {
            CheckReadyState();
            CheckCommandType(Cmd, CommandTypes.List);

            List<string> Results = new List<string>();

            // List all scripts.
            if (Cmd.Target == "*") 
            {
                Results = new List<string>(_ListOfScripts); 
            }

            // List all scripts that start with the specified letter or number.
            if (Char.IsLetter(Cmd.Target[0]) || Char.IsNumber(Cmd.Target[0]))
            {
                string TargetChar = Cmd.Target[0].ToString().ToLower();
                Results = new List<string>(Array.FindAll(_ListOfScripts, ScriptName => ScriptName.ToLower().StartsWith(TargetChar)));
            }

            // List all scripts that are of a specific type.
            if (Cmd.Target.StartsWith(".") && Cmd.Target.Length > 1)
            {
                Results = new List<string>(Array.FindAll(_ListOfScripts, ScriptName => ScriptName.Contains(Cmd.Target)));
            }

            // Save results.
            _CurrentOrder = new Geta(Results, OutputsTo.Screen, true);
        }

        /// <summary>Finds and returns the text of the |Info| tag from the script specified in the Command.</summary>
        /// <param name="Cmd">Command to process. Only 'info' commands will run.</param>
        public void ProvideInfo(Command Cmd)
        {
            CheckReadyState();
            CheckCommandType(Cmd, CommandTypes.Info);

            string ScriptName = Cmd.Target.ToLower();

            CheckScriptBin(ScriptName);

            string InfoText = "";

            string ScriptText = _ScriptFileBin[ScriptName];
            string InfoTag = "|" + Cmd.Action.ToLower() + "|";                
                
            int OpenTagIndex = ScriptText.ToLower().IndexOf(InfoTag);
            int CloseTagIndex = ScriptText.ToLower().LastIndexOf(InfoTag);

            if ( (CloseTagIndex > -1) && (OpenTagIndex > -1) && (CloseTagIndex != OpenTagIndex) )
            {
                InfoText = ScriptText.Remove(CloseTagIndex, (ScriptText.Length - CloseTagIndex));
                InfoText = InfoText.Remove(0, OpenTagIndex + InfoTag.Length);                        
            }
            else 
            {
                InfoText = "So sorry, no |" + InfoTag + "| found for " + Cmd.Target + ", or section is improperly tagged.";
            }

            _CurrentOrder = new Geta();
            _CurrentOrder.AddResult(InfoText);
        }

        /// <summary>Searches scripts stored in memory for a specific string.</summary>
        /// <param name="Cmd">Command to process. Only 'search' commands will run.</param>
        public void SearchScripts(Command Cmd)
        {
            CheckReadyState();
            CheckCommandType(Cmd, CommandTypes.Search);

            List<string> Results = new List<string>();

            string TextToFind = Cmd.Target.ToLower();

            foreach (KeyValuePair<string,string> Pair in _ScriptFileBin)
            {
                string ScriptText = Pair.Value.ToLower();
                if (ScriptText.Contains(TextToFind))
                {
                    Results.Add(Pair.Key);
                }
            }

            _CurrentOrder = new Geta(Results, OutputsTo.Screen, true);
        }

        /// <summary>Runs the specified script.</summary>
        /// <param name="Cmd">Command to process. Only 'run' commands will run.</param>
        public void RunScript(Command Cmd)
        {
            CheckReadyState();
            CheckCommandType(Cmd, CommandTypes.Run);

            string ScriptName = Cmd.Target.ToLower();

            CheckScriptBin(ScriptName);

            Omakase CommandBuilder = new Omakase( (_ScriptsFolder + ScriptName), Cmd.Params);

            Process p = Process.Start(CommandBuilder.ExecuteWith, CommandBuilder.CommandStatement);
            p.WaitForExit();
            p.Close();
        }

        /// <summary>Runs the specified query script and returns the results.</summary>
        /// <param name="Cmd">Command to process. Only 'query' commands will run.</param>
        public void ExecuteQuery(Command Cmd)
        {
            CheckReadyState();
            CheckCommandType(Cmd, CommandTypes.Query);

            Query CurrentQuery = new Query(Cmd, _ScriptsFolder);

            QueryProcessor Processor = new QueryProcessor();
            Processor.Process(CurrentQuery);

            _CurrentOrder = new Geta(Processor.Results, CurrentQuery.QueryOutput, false);
            Processor.Dispose();
        }

        #endregion

        #region Private Methods

        /// <summary>Checks the state of the class. If not ready, an exception is thrown.</summary>
        /// <remarks>This check should be used in each externally accessible function before any user requests are processed.</remarks>
        private void CheckReadyState()
        {
            if ((!_Ready) || (!Directory.Exists(_ScriptsFolder)))
            {
                throw new ApplicationException("Not in a Ready state; unable to proceed. Make sure you are specifying the correct location of your 'scripts' folder.");
            }
        }

        /// <summary>Checks the Type of a Command. If not the ExpectedType, an exception is thrown.</summary>
        /// <remarks>This check should be used in each externally accessible function before any user requests are processed.</remarks>
        private void CheckCommandType(Command ProvidedCommand, CommandTypes ExpectedType)
        { 
            if (ProvidedCommand.CommandType != ExpectedType)
            {
                throw new ArgumentException("Type value of the specified Command did not match expected type.");
            }
        }

        /// <summary>Builds a dictionary of files and their contents.</summary>
        /// <param name="Folder">Folder containing scripts.</param>
        /// <param name="FileList">Script names</param>
        /// <param name="FileBin">Dictionary to store the records in.</param>
        /// <remarks>IMPORTANT!: File name (key) is stored in ToLower state to standardize casing.
        /// This needs to be accounted for whenever a lookup is performed.</remarks>
        private void BuildFileBin(string Folder, string[] FileList, Dictionary<string,string> FileBin)
        { 
            foreach(string File in FileList)
            {
                string FileContents = "";
                using (StreamReader Reader = new StreamReader(Folder + "\\" + File))
                {
                    FileContents = Reader.ReadToEnd();
                }
                
                FileBin.Add(File.ToLower(), FileContents);
            }
        }

        /// <summary>Checks the ScriptFileBin for the specified script. If not found, an exception is thrown.</summary>
        /// <param name="ScriptName">The script to look for.</param>
        private void CheckScriptBin(string ScriptName)
        {
            if (!_ScriptFileBin.ContainsKey(ScriptName)) 
            {
                throw new KeyNotFoundException(ScriptName + " was not found in your list of scripts.");
            }
        }

        #endregion
        
        #region Properties / Variables

        /// <summary>Determines whether or not the class is in a state where it can run functions.</summary> 
        private bool _Ready = false;
        public bool Ready
        { get { return _Ready; } }

        /// <summary> Location of script files.</summary>
        private string _ScriptsFolder;

        /// <summary>List of script files found in _ScriptsFolder.</summary> 
        private string[] _ListOfScripts;

        /// <summary>Collection of script files found in _ScriptsFolder and their contents.</summary> 
        private Dictionary<string,string> _ScriptFileBin;

        /// <summary>Stores the results (or order) from a command request. Value changes any time a command is run.</summary>
        private Geta _CurrentOrder;
        public Geta CurrentOrder
        { get { return _CurrentOrder; } }

        #endregion
    }
}
